#include <utils/io.h>
#include <utils/string.h>
#include <time/time.h>

#define CMOS_ADDRESS 0x70
#define CMOS_DATA    0x71

#define SECONDS_REG  0x00
#define MINUTES_REG  0x02
#define HOUR_REG     0x04
#define WEEKDAY_REG  0x06
#define DAY_REG      0x07
#define MONTH_REG    0x08
#define YEAR_REG     0x09
#define STATUS_A     0x0A
#define STATUS_B     0x0B

#define SECONDS      0
#define MINUTES      1
#define HOUR         2
#define DAY          3
#define MONTH        4
#define YEAR         5
#define CENTURY      6

#define DATETIME_LEN 7

// index to register map
uint8 idx2reg[DATETIME_LEN] = { SECONDS_REG, MINUTES_REG, HOUR_REG, DAY_REG, MONTH_REG, YEAR_REG, 0xFF /* CENTURY */ };

uint8 safe_read_rtc(int r) {
    cli();
    outb(CMOS_ADDRESS, r);
    uint8 rv = inb(CMOS_DATA);
    sti();
    return rv;
}

uint8 read_rtc(int r) {
    outb(CMOS_ADDRESS, r);
    uint8 rv = inb(CMOS_DATA);
    return rv;
}

uint8 is_update_in_progress() {
    uint8 statusA = safe_read_rtc(STATUS_A);
    return statusA & 0x80;
}

void wait_for_update() {
    while (is_update_in_progress());
}

void update_from_rtc(uint8 *current) {
    cli();
    uint8 i = 0;
    for(i = 0; i < DATETIME_LEN; i++) {
        if (idx2reg[i] != 0xFF) {
            current[i] = read_rtc(idx2reg[i]);
        }
    }
    // FIXME: we need to change this using ACPI
    if (idx2reg[CENTURY] == 0xFF) {
        current[CENTURY] = 20;
    }
    sti();
}

int64 array_to_time(uint8* current) {
    struct tm t;
    t.tm_sec = current[SECONDS];
    t.tm_min = current[MINUTES];
    t.tm_hour = current[HOUR];
    t.tm_mday = current[DAY];
    t.tm_mon = current[MONTH];
    t.tm_year = current[CENTURY] * 100 + current[YEAR];
    // t.tm_wday
    // t.tm_yday
    // t.tm_isdst

    return mktime(&t);
}

// NOTE: NEVER USE THIS FUNCTION OUTSIDE OF OS INITIALIZATION
// IT'S TOO SLOW
int64 get_rtc_time() {
    uint8 current[DATETIME_LEN];

    wait_for_update();

    update_from_rtc(current);

    uint8 last[DATETIME_LEN];
    memset(last, 0, DATETIME_LEN);
    do {
        memcpy(last, current, DATETIME_LEN);
        
        wait_for_update();
        update_from_rtc(current);
    } while (!memcmp(last, current, DATETIME_LEN));

    uint8 statusB = safe_read_rtc(STATUS_B);

    // BCD conversion
    if (!(statusB & 0x04)) {
        uint8 i = 0;
        for (i = 0;i < DATETIME_LEN;i++) {
            if (idx2reg[i] != 0xFF) {
                current[i] = (current[i] & 0x0F) + ((current[i] / 16) * 10);
            }
        }
    }
    
    // 12 hour to 24 hour conversion
    if (!(statusB & 0x02) && (current[HOUR] & 0x80)) {
        current[HOUR] = ((current[HOUR] & 0x7F) + 12) % 24;
    }

    return array_to_time(current);
}

